package de.bk.timecalc.gui;

import static de.bk.timecalc.gui.GuiToolbox.*;

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import de.bk.timecalc.core.ChallengeDTO;
import de.bk.timecalc.core.TimeCalcCore;
import de.bk.timecalc.core.TimeDTO;

/**
 * Controls the behaviour of the application.
 * 
 * @author Bastian Krol
 */
public class GameWindowBehaviour
{
  private TimeCalcCore timeCalcCore;
  private GameWindowLayout layoutDelegate;
  private Clock clock;
  private ScheduledFuture<?> clockFuture;
  private ScheduledThreadPoolExecutor executor;
  private boolean paused;
  private SettingsWindow settingsWindow;
  private SettingsModel settingsModel;
  private HelpWindow helpWindow;

  GameWindowBehaviour(SettingsModel settingsModel)
  {
    this.settingsModel = settingsModel;
    this.createTimeCalcCore();
    this.layoutDelegate = new GameWindowLayout(this);
    this.executor = new ScheduledThreadPoolExecutor(1);
  }

  private void createTimeCalcCore()
  {
    this.timeCalcCore = new TimeCalcCore();
    this.transferSettings(this.settingsModel);
  }

  void init(Display display)
  {
    this.layoutDelegate.initShell(display);
    this.resetStats();
  }

  Shell getShell()
  {
    return this.layoutDelegate.getShell();
  }

  void openShell()
  {
    this.layoutDelegate.openShell();
  }

  void nextChallenge()
  {
    this.stopClock();
    ChallengeDTO challenge = this.timeCalcCore.nextChallenge();
    this.layoutDelegate.textItemTakenAt.setText(challenge.getItem().getName());
    this.fillClockFromTimeDTO(this.layoutDelegate.textClockTakenAtMinute,
        this.layoutDelegate.textClockTakenAtSecond, challenge.getTime());
    this.layoutDelegate.textItemWillRespawnAt.setText(challenge.getItem()
        .getName());
    this.layoutDelegate.textClockWillRespawnAtMinute.setText("");
    this.layoutDelegate.textClockWillRespawnAtSecond.setText("");
    if (!this.settingsModel.isIgnoreMinute())
    {
      this.layoutDelegate.textClockWillRespawnAtMinute.setFocus();
    }
    else
    {
      this.layoutDelegate.textClockWillRespawnAtMinute.setText(String
          .valueOf(this.timeCalcCore.getAnswerForLastChallenge().getMinute()));
      this.layoutDelegate.textClockWillRespawnAtSecond.setFocus();

    }
    this.startClock((challenge.getTime().getMinute() * 60)
        + challenge.getTime().getSecond());
  }

  private void stopClock()
  {
    if (this.clockFuture != null)
    {
      this.clockFuture.cancel(false);
    }
  }

  private void startClock(final int time)
  {
    this.clock = new Clock(time);
    this.clockFuture = this.executor.scheduleAtFixedRate(clock, 0, 1,
        TimeUnit.SECONDS);
  }

  private void startClockAgainAfterPause()
  {
    this.clockFuture = this.executor.scheduleAtFixedRate(clock, 0, 1,
        TimeUnit.SECONDS);
  }

  private void updateFromClock(final String time)
  {
    Shell shell = this.layoutDelegate.getShell();
    Display display = shell.getDisplay();
    display.syncExec(new Runnable()
    {
      @Override
      public void run()
      {
        Text textBigClock = GameWindowBehaviour.this.layoutDelegate.textBigClock;
        if (!textBigClock.isDisposed())
        {
          textBigClock.setText(time);
        }
      }
    });
  }

  void checkAnswerAndStartNextChallenge()
  {
    TimeDTO answer = this.buildAnswerFromTextFields();
    if (answer == null)
    {
      return;
    }
    boolean correct = this.timeCalcCore.checkAnswerForLastChallenge(answer);
    this.udpateStats(correct);
    this.nextChallenge();
  }

  private void udpateStats(boolean correct)
  {
    if (correct)
    {
      this.layoutDelegate.textStatLastAnswerRightWrong.setText("Right");
    }
    else
    {
      this.layoutDelegate.textStatLastAnswerRightWrong.setText("Wrong");
    }
    TimeDTO correctAnswerForLastChallenge = this.timeCalcCore
        .getAnswerForLastChallenge();
    this.fillTextFromTimeDTO(this.layoutDelegate.textStatLastChallengeSolution,
        correctAnswerForLastChallenge);

    this.layoutDelegate.textStatTimeNeededForLastChallenge
        .setText(this.timeCalcCore.getTimeNeededForLastAnswer().toString());
    this.layoutDelegate.textStatScoreForLastChallenge
        .setText(floatToString(this.timeCalcCore.getScoreForLastAnswer()));
    this.layoutDelegate.textStatOverallScore
        .setText(floatToString(this.timeCalcCore.getScore()));
    this.layoutDelegate.textStatAverageScore
        .setText(floatToString(this.timeCalcCore.getScore()
            / (float) this.timeCalcCore.getChallengesDone()));
  }

  private void resetStats()
  {

    this.layoutDelegate.textStatLastAnswerRightWrong.setText("-");
    this.layoutDelegate.textStatLastChallengeSolution.setText("-");

    this.layoutDelegate.textStatTimeNeededForLastChallenge.setText("00:000");
    this.layoutDelegate.textStatScoreForLastChallenge.setText(floatToString(0));
    this.layoutDelegate.textStatOverallScore.setText(floatToString(0));
    this.layoutDelegate.textStatAverageScore.setText(floatToString(0));
  }

  private TimeDTO buildAnswerFromTextFields()
  {
    String minuteString = this.layoutDelegate.textClockWillRespawnAtMinute
        .getText();
    String secondString = this.layoutDelegate.textClockWillRespawnAtSecond
        .getText();
    int minute = this.parseTextField(minuteString, "minute");
    if (minute == -1)
    {
      return null;
    }
    int second = this.parseTextField(secondString, "second");
    if (second == -1)
    {
      return null;
    }
    return new TimeDTO(minute, second);
  }

  private int parseTextField(String text, String label)
  {
    if (text == null || text.equals(""))
    {
      openOkPopup(this.getShell(), "Invalid Time", "You did not enter a "
          + label + ".", SWT.ICON_ERROR);
      return -1;
    }
    int value;
    try
    {
      value = Integer.parseInt(text);
    }
    catch (NumberFormatException e)
    {
      openOkPopup(this.getShell(), "Invalid Time", label.substring(0, 1)
          .toUpperCase()
          + label.substring(1) + " is not numeric.", SWT.ICON_ERROR);
      return -1;
    }
    if (value < 0)
    {
      openOkPopup(this.getShell(), "Invalid Time", label.substring(0, 1)
          .toUpperCase()
          + label.substring(1) + " is negative.", SWT.ICON_ERROR);
      return -1;
    }
    else if (value > 59)
    {
      openOkPopup(this.getShell(), "Invalid Time", label.substring(0, 1)
          .toUpperCase()
          + label.substring(1) + " is greater than 59.", SWT.ICON_ERROR);
      return -1;
    }
    return value;
  }

  private void fillClockFromTimeDTO(Text textClockMinute, Text textClockSecond,
      TimeDTO time)
  {
    textClockMinute.setText(intToTwoDigitString(time.getMinute()));
    textClockSecond.setText(intToTwoDigitString(time.getSecond()));
  }

  private void fillTextFromTimeDTO(Text text, TimeDTO time)
  {
    text.setText(intToTwoDigitString(time.getMinute()) + ":"
        + intToTwoDigitString(time.getSecond()));
  }

  void reset()
  {
    this.createTimeCalcCore();
    this.resetStats();
    this.nextChallenge();
  }

  void togglePause()
  {
    this.togglePauseInternal();
    this.layoutDelegate.togglePauseInternal();
  }

  private void togglePauseInternal()
  {
    this.paused = !this.paused;
    if (this.paused)
    {
      this.timeCalcCore.pause();
      this.stopClock();
    }
    else
    {
      this.timeCalcCore.startAgainAfterPause();
      this.startClockAgainAfterPause();
    }
  }

  boolean isPaused()
  {
    return this.paused;
  }

  void dispose()
  {
    this.clockFuture.cancel(false);
    this.executor.shutdownNow();
    this.layoutDelegate.dispose();
  }

  private final class Clock implements Runnable
  {
    private int seconds;

    private Clock()
    {
      this(0);
    }

    private Clock(int secondsStart)
    {
      this.seconds = secondsStart;
    }

    @Override
    public void run()
    {
      int minuteClock = this.seconds / 60;
      int secondsClock = this.seconds % 60;
      String time = GuiToolbox.intToTwoDigitString(minuteClock) + ":"
          + GuiToolbox.intToTwoDigitString(secondsClock);
      GameWindowBehaviour.this.updateFromClock(time);
      switch (GameWindowBehaviour.this.settingsModel.getTimerDirection())
      {
        case UP:
          this.seconds++;
          break;
        case DOWN:
          if (this.seconds > 0)
          {
            this.seconds--;
          }
          break;
        default:
          throw new AssertionError();
      }
    }
  }

  void setSettingsWindow(SettingsWindow settingsWindow)
  {
    this.settingsWindow = settingsWindow;
  }

  void openSettingsWindow()
  {
    SettingsModel temporarySettingsModel = new SettingsModel(this.settingsModel);
    this.settingsWindow.show(temporarySettingsModel);
  }

  void setHelpWindow(HelpWindow helpWindow)
  {
    this.helpWindow = helpWindow;
  }

  void openHelpWindow()
  {
    this.helpWindow.show();
  }

  void openAboutWindow()
  {
    openOkPopup(
        this.layoutDelegate.getShell(),
        "Quake Live Time Calc Trainer",
        "Slapped together by Bastian Krol. Published under GNU General Public License v3. "
            + "Have a look at http://code.google.com/p/qltimecalc/ for the latest version (or use \"Project Website\" from the Help menu). "
            + "Quake Live, Quake and Quake 3 Arena are registered trademarks of Id Software LLC.",
        SWT.ICON_INFORMATION);
  }

  void transferSettings(SettingsModel settingsModel)
  {
    this.settingsModel = settingsModel;
    if (this.timeCalcCore.getTimerDirection() != this.settingsModel
        .getTimerDirection())
    {
      this.timeCalcCore.setTimerDirection(this.settingsModel
          .getTimerDirection());
    }
    this.timeCalcCore.setItemDistribution(this.settingsModel
        .getItemDistribution());
    this.timeCalcCore.setRoundToFiveSeconds(this.settingsModel
        .isRoundToFiveSeconds());
    if (this.layoutDelegate != null)
    {
      this.layoutDelegate.textClockWillRespawnAtMinute
          .setEnabled(!this.settingsModel.isIgnoreMinute());
      this.layoutDelegate.textClockWillRespawnAtMinute.setText(String
          .valueOf(this.timeCalcCore.getAnswerForLastChallenge().getMinute()));
    }
  }
}
